﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Drawing;

namespace Face_Recognition
{
    public static class Morphology
    {
        private static List<int[]> points;
        private static bool[,] finalMask;
        private static List<Hole> holes = new List<Hole>();
        private static List<SkinRegion> skinRegions = new List<SkinRegion>();
        private static bool[,] morphMask;
        private static bool[,] morphMask1;
        private static bool[,] morphMask2;
        private static bool[,] morphMask3;
        private static bool[,] morphMask4;
        private static bool[,] morphMask5;
        private static bool[,] morphMask6;
        private static bool[,] morphMask7;
        private static bool[,] edgeMask;
        private static Bitmap bitmap;
        private static float skinRatio;
        private static int shrinkValue;

        public static int ShrinkValue
        {
            get { return Morphology.shrinkValue; }
            set { Morphology.shrinkValue = value; }
        }
        private static int width;
        private static int height;

        private static bool[,] Dilatation(bool[,] image, int scale)
        {
            int height = image.GetLength(0);
            int width = image.GetLength(1);
            bool[,] mask = new bool[height,width];
            int range = scale / 2;

            for (int w = scale; w < width - scale; w++)
            {
                for (int h = scale; h < height - scale; h++)
                {
                    if (image[h, w] == true)
                    {
                        for (int i = -scale / 2; i < scale / 2; i++)
                        {
                            for (int j = -scale / 2; j < scale / 2; j++)
                            {
                                if (Math.Abs(i + j) <= range)
                                    mask[h + j, w + i] = true;
                            }
                        }
                    }
                }
            }
            return mask;
        }

        // private Queue<int[]> holes = new Queue<int[]>();

        public static T DeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T)formatter.Deserialize(ms);
            }
        }

        private static bool[,] Erosion(bool[,] image, int scale)
        {

            bool[,] mask = DeepClone<bool[,]>(image);
            int range = scale / 2;
            int height = image.GetLength(0);
            int width = image.GetLength(1);

            for (int w = scale; w < width - scale; w++)
            {
                for (int h = scale; h < height - scale; h++)
                {
                    if (image[h, w] == false)
                    {
                        for (int i = -scale / 2; i < scale / 2; i++)
                        {
                            for (int j = -scale / 2; j < scale / 2; j++)
                            {
                                if (Math.Abs(i + j) <= range)
                                    mask[h + j, w + i] = false;
                                //holes.Enqueue(new int[2] { h + j, w + i });
                            }
                        }
                    }
                }
            }

            return mask;
        }

        public static bool[,] Thinning(bool[,] image)
        {
            bool[,] mask = DeepClone<bool[,]>(image);
            bool fit=true;

            byte[][][] elements = new byte[8][][];

            //structural elements needed for thinning - two rotated by 90 degrees 
            elements[0] = new byte[3][] { new byte[] { 0, 0, 0 }, new byte[] { 2, 1, 2 }, new byte[] { 1, 1, 1 } };
            elements[1] = new byte[3][] { new byte[] { 1, 1, 1 }, new byte[] { 2, 1, 2 }, new byte[] { 0, 0, 0 } };
            elements[2] = new byte[3][] { new byte[] { 1, 2, 0 }, new byte[] { 1, 1, 0 }, new byte[] { 1, 2, 0 } };
            elements[3] = new byte[3][] { new byte[] { 0, 2, 1 }, new byte[] { 0, 1, 1 }, new byte[] { 0, 2, 1 } };
            elements[4] = new byte[3][] { new byte[] { 2, 0, 0 }, new byte[] { 1, 1, 0 }, new byte[] { 2, 1, 2 } };
            elements[5] = new byte[3][] { new byte[] { 0, 0, 2 }, new byte[] { 0, 1, 1 }, new byte[] { 2, 1, 2 } };
            elements[6] = new byte[3][] { new byte[] { 0, 0, 2 }, new byte[] { 0, 1, 1 }, new byte[] { 2, 1, 2 } };
            elements[7] = new byte[3][] { new byte[] { 2, 1, 2 }, new byte[] { 1, 1, 0 }, new byte[] { 2, 0, 0 } };

            int height = image.GetLength(0);
            int width = image.GetLength(1);

            for (int c = 0; c < 2; c++)
            {
                for (int e = 0; e < 8; e++)
                {
                    for (int w = 1; w < width - 1; w++)
                    {
                        for (int h = 1; h < height - 1; h++)
                        {
                            if (image[h, w] == true)
                            {
                                fit = true;

                                for (int i = -1; i <= 1; i++)
                                {
                                    for (int j = -1; j <= 1; j++)
                                    {
                                        if ((elements[e][i + 1][j + 1] == 1 && mask[h + j, w + i] || elements[e][i + 1][j + 1] == 0 && !mask[h + j, w + i]) || elements[e][i + 1][j + 1] == 2)
                                        {

                                        }
                                        else
                                        {
                                            fit = false; 
                                            break;
                                        }
                                    
                                    }
                                    if (fit == false) break;
                                }

                                if (fit) 
                                    mask[h, w] = false;
                            }
                        }
                    }
                }
            }

            return mask;
        }

        private static bool[,] FindHolesMask(bool[,] image)
        {

            for (int w = 10; w < width; w++)
            {
                for (int h = 10; h < height; h++)
                {
                    if (morphMask4[h, w] == false && morphMask6[h, w] == true)
                    {
                        //holes.Enqueue(new int[2] { w, h });
                        morphMask[h, w] = true;
                        //bitmap.SetPixel(w, h, Color.White);
                    }
                    else
                    {
                        morphMask[h, w] = false;
                        //bitmap.SetPixel(w, h, Color.Black);
                    }
                }
            }

            return morphMask;
        }

        private static bool[,] FindSkinRegionHolesMask(bool[,] image)
        {

            for (int w = 1; w < image.GetLength(1) -1; w++)
            {
                for (int h = 1; h < image.GetLength(0) -1; h++)
                {
                    if (morphMask7[h, w] == false && morphMask2[h, w] == true)
                    {
                        //holes.Enqueue(new int[2] { w, h });
                        morphMask[h, w] = true;
                        //bitmap.SetPixel(w, h, Color.White);
                    }
                    else
                    {
                        morphMask[h, w] = false;
                        //bitmap.SetPixel(w, h, Color.Black);
                    }
                }
            }

            return morphMask;
        }

        private static bool[,] CreateHoles(bool[,] image)
        {
            for (int w = 1; w < finalMask.GetLength(1) - 1; w++)
            {
                for (int h = 1; h < finalMask.GetLength(0) - 1; h++)
                {
                    if (finalMask[h, w] == true)
                    {
                        Hole hole = new Hole(expandHole(h, w));

                        if (hole.Coordinates.Count > 3)
                            holes.Add(hole);
                        //morphMask[h, w] = true;
                            bitmap.SetPixel(w, h, Color.Red);
                    }
                    else
                    {
                        //morphMask[h, w] = false;
                        //bitmap.SetPixel(w, h, Color.Black);
                    }
                }
            }

            return morphMask;
        }

        private static bool[,] CreateSkinRegions(bool[,] image)
        {

            for (int w = 1; w < bitmap.Width / shrinkValue - 1; w++)
            {
                for (int h = 1; h < bitmap.Height / shrinkValue - 1; h++)
                {
                    if (image[h, w] == true)
                    {
                        SkinRegion region = new SkinRegion(expandSkinRegion(h, w));

                        if (region.Coordinates.Count > 100)
                            skinRegions.Add(region);
                            //morphMask[h, w] = true;
                            //bitmap.SetPixel(w, h, Color.Green);
                    }
                    else
                    {
                        //morphMask[h, w] = false;
                        //bitmap.SetPixel(w, h, Color.Black);
                    }
                }
            }

            return morphMask;
        }

        private static bool[,] Shrink(bool[,] skinMask)
        {
            if (shrinkValue == 1) return skinMask;

            bool[,] mask = new bool[height, width];
            bool[,] tex = new bool[(int)shrinkValue, (int)shrinkValue];
            short texCount;
            int skinCount = 0;
            for (int w = 0; w < skinMask.GetLength(1) - shrinkValue; w += (int)shrinkValue)
            {
                for (int h = 0; h < skinMask.GetLength(0) - shrinkValue; h += (int)shrinkValue)
                {
                    tex = new bool[2, 2] { { false, false }, { false, false } };
                    texCount = 0;
                    for (int i = 0; i < 3; i++)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if (skinMask[h + i, w + j] == true)
                            {
                                texCount++;
                                skinCount++;
                            }
                        }
                    }

                    if (texCount > (0.5 * shrinkValue * shrinkValue + 1))
                        mask[h / shrinkValue, w / shrinkValue] = true;

                }
            }

            skinRatio = skinCount / (bitmap.Width * bitmap.Height * shrinkValue * shrinkValue);

            return mask;
        }
        
        public static List<Hole> FindHoles(bool[,] skinMask, Bitmap _bitmap)
        {
                bitmap = _bitmap;
                //

                shrinkValue = bitmap.Width < bitmap.Height ? bitmap.Width / 400 : bitmap.Height / 400;
                if (shrinkValue == 0) shrinkValue = 1;

                shrinkValue = 1;

                width = (int)(bitmap.Width / shrinkValue);
                height = (int)(bitmap.Height / shrinkValue);

                finalMask = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask1 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask2 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask3 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask4 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask5 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask6 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                morphMask7 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
                //
               
                int structSize;
 
                structSize = bitmap.Width > bitmap.Height ? bitmap.Width / 100 : bitmap.Height / 100;

                if (structSize < 4) structSize = 4;

                //morphMask = Dilatation(Shrink(),3);
                morphMask1 = Dilatation(Shrink(skinMask), (structSize / 4) % 2 == 0 ? structSize / 4 : (structSize / 4) +1 );
                morphMask2 = Erosion(morphMask1, (structSize / 4) % 2 == 0 ? structSize / 4 : (structSize / 4) + 1);
                morphMask3 = Dilatation(morphMask2, (structSize / 4) % 2 == 0 ? structSize / 2 : (structSize / 2) + 1); //was 4
                morphMask4 = Erosion(morphMask3, (structSize / 4) % 2 == 0 ? structSize / 2 : (structSize / 2) + 1); //was                

                morphMask5 = Dilatation(morphMask4,(int)(structSize * 2));
                morphMask6 = Erosion(morphMask5, (int)(structSize * 2));

                //FindHolesMask(morphMask);

                morphMask7 = Erosion(morphMask, 2);
                finalMask = DeepClone<bool[,]>(morphMask7);

                //CreateSkinRegions(morphMask7);
                CreateHoles(finalMask);

                return holes;

                //return bitmap;
        }

        public static List<SkinRegion> FindSkinRegions(List<bool[,]> skinMask, Bitmap _bitmap)
        {
            bitmap = _bitmap;
            //
            skinRegions = new List<SkinRegion>();
            edgeMask = skinMask[1];
            shrinkValue = bitmap.Width < bitmap.Height ? bitmap.Width / 400 : bitmap.Height / 400;
            if (shrinkValue == 0) shrinkValue = 1;

            shrinkValue = 1;

            width = (int)(bitmap.Width / shrinkValue);
            height = (int)(bitmap.Height / shrinkValue);

            finalMask = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask1 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask2 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask3 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask4 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask5 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask6 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask7 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            //

            int structSize;

            structSize = bitmap.Width > bitmap.Height ? bitmap.Width / 100 : bitmap.Height / 100;

            if (structSize < 4) structSize = 4;

            morphMask1 = Dilatation(Shrink(skinMask[0]), (structSize / 4) % 2 == 0 ? structSize / 4 : (structSize / 4) + 1);
            morphMask2 = Erosion(morphMask1, (structSize / 4) % 2 == 0 ? structSize / 4 : (structSize / 4) + 1);

            morphMask6 = Erosion(morphMask2, 4);
            morphMask5 = DeepClone<bool[,]>(morphMask6);
            CreateSkinRegions(morphMask6);

            return skinRegions;
        }

        public static List<Hole> FindHolesInRegion(SkinRegion skinRegion)
        {
            Rectangle rectangle = skinRegion.BoundingBox;

            finalMask = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask = new bool[rectangle.Height, rectangle.Width];
            morphMask1 = new bool[rectangle.Height, rectangle.Width];
            morphMask2 = new bool[rectangle.Height, rectangle.Width];
            morphMask3 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask4 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            //morphMask5 = new bool[(int)(bitmap.Height / shrinkValue), (int)(bitmap.Width / shrinkValue)];
            morphMask7 = new bool[rectangle.Height,rectangle.Width];
            //

            int structSize;

            structSize = bitmap.Width > bitmap.Height ? bitmap.Width / 100 : bitmap.Height / 100;

            if (structSize < 4) structSize = 4;

            for (int h = 0; h < rectangle.Height; h++)
            {
                for (int w = 0; w < rectangle.Width; w++)
                {
                    morphMask7[h, w] = morphMask5[h + rectangle.Y, w + rectangle.X];
                }
            }

            morphMask1 = Dilatation(morphMask7, (int)(10));
            morphMask2 = Erosion(morphMask1, (int)(12));

            FindSkinRegionHolesMask(morphMask1);

            holes = new List<Hole>();

            finalMask = DeepClone<bool[,]>(morphMask);

            //CreateSkinRegions(morphMask7);
            CreateHoles(finalMask);

            return holes;

            //return bitmap;
        }

        private static List<int[]> expandHole(int y, int x)
        {
            Queue<int[]> points = new Queue<int[]>();
            List<int[]> holeArea = new List<int[]>();
            //int[] point = new int[2];
         
            points.Enqueue(new int[2] { x, y });
            finalMask[y, x] = false;

            while (points.Count > 0)
            {           
                
                holeArea.Add(points.Dequeue());
               
                x = holeArea.Last()[0];
                y = holeArea.Last()[1];

                for(int i=-1; i<=1;i++)
                {
                    for(int j=-1; j<=1;j++)
                    {
                        if (finalMask[y + j, x + i] == true &&
                            !points.Contains(new int[2] {x + i, y+j}) &&
                            !holeArea.Contains(new int[2] {x + i, y+j}))
                            {
                                points.Enqueue(new int[2] { x + i, y + j });
                                finalMask[y + j, x + i] = false;
                            }       
                    }
                }
             
            }

            return holeArea;
        }

        private static List<int[]> expandSkinRegion(int y, int x)
        {
            Queue<int[]> points = new Queue<int[]>();
            List<int[]> holeArea = new List<int[]>();
            //int[] point = new int[2];

            points.Enqueue(new int[2] { x, y });
            morphMask6[y, x] = false;

            while (points.Count > 0)
            {
                holeArea.Add(points.Dequeue());

                x = holeArea.Last()[0];
                y = holeArea.Last()[1];

                for (int i = -1; i <= 1; i++)
                {
                    for (int j = -1; j <= 1; j++)
                    {
                        if (morphMask6[y + j, x + i] == true &&
                            !points.Contains(new int[2] { x + i, y + j }) &&
                            !holeArea.Contains(new int[2] { x + i, y + j })
                            )
                        {
                           if((Settings.SobelBoundaries || Settings.ColorSobelBoundaries) && ((i==0 || j==0) && !edgeMask[y+j,x+i] ||
                               (i == 1 && j == 1 && (!edgeMask[y + 1, x] || !edgeMask[y, x + 1])) ||
                               (i == -1 && j == -1 && (!edgeMask[y - 1, x] || !edgeMask[y, x - 1])) ||
                               (i == -1 && j == 1 && (!edgeMask[y, x - 1] || !edgeMask[y + 1, x])) ||
                               (i == 1 && j == -1 && (!edgeMask[y + 1, x] || !edgeMask[y, x - 1]))
                               ))
                            {
                                points.Enqueue(new int[2] { x + i, y + j });
                                morphMask6[y + j, x + i] = false;
                            }
                        }
                    }
                }

            }

            return holeArea;
        }
    }
}
